/**
 * Mongodb接收者
 * Copyright(C) 2018 liumurong
 */

import { Receiver } from './receiver';
import { Readable } from 'stream';
import { MongoReadStream } from './mongoreadstream';
import { FileSlice, MetadataOptions } from './models';

import { Collection, MongoClient, Db } from 'mongodb';
import { MerkleTree } from 'merkletreets';

/**
 * Mongodb
 */
export class MongoReceiver extends Receiver {
	public db: Db;
	public col: Collection;
	public filesCol: Collection;
	public merkleCol: Collection;
	public collectionName: string;
	public filesCollectionName: string;
	public unIntergalTimeSpan: number;
	public merkleCollectionName: string;


	/**
	 * MongodbReceiver
	 * @param connstr 数据库链接字符串
	 */
	constructor(connstr: string) {
		super(connstr);
		this.collectionName = 'fdchunks';
		this.filesCollectionName = 'fdfiles';
		this.merkleCollectionName = 'fdmerkle';
		this.unIntergalTimeSpan = 7 * 24 * 60 * 60 * 1000;
	}

	/**
	 * 连接数据库
	 */
	async connect() {
		if (!this.db) {
			this.db = await MongoClient.connect(this.target);
			this.col = this.db.collection(this.collectionName);
			this.filesCol = this.db.collection(this.filesCollectionName);
			this.merkleCol = this.db.collection(this.merkleCollectionName);
		}
	}

	/**
	 * 保存切片
	 * @param slice 文件切片
	 */
	async saveSlice(slice: FileSlice) {
		// 保存切片
		await this.connect();
		await this.col.insertOne(slice);
		// 完整性验证
		let hashTreeCol = await this.merkleCol.findOne({ hash: slice.hash });
		if (!hashTreeCol) {
			hashTreeCol = new MerkleTree({ leafcount: slice.slicecount })
		} else {
			const hashTree = hashTreeCol.hashTree;
			hashTreeCol = new MerkleTree({ leafcount: slice.slicecount, hashTree })
		}
		hashTreeCol.updateLeaf(slice.index, slice.data);
		hashTreeCol.update();
		// 更新记录
		const { hash, name, slicecount, size } = slice;
		const ctime = new Date().valueOf();
		await this.merkleCol.updateOne({ hash: slice.hash }, { $set: { hash: slice.hash, ctime, hashTree: hashTreeCol } }, { upsert: true });
		await this.filesCol.updateOne({ hash }, { $set: { hash, name, slicecount, size, ctime, root: hashTreeCol.root().hash } }, { upsert: true });
	}

	/**
	 * 获取文件流
	 * @param hash 文件MD5
	 */
	getStream(hash: string): Readable {
		return new MongoReadStream(hash, this.target, this);
	}

	/*
     * 获取已经上传了多少切片
     */
	async getNodeHash(hash: string, depth: string, index: string): Promise<string> {
		await this.connect();
		let hashTreeCol = await this.merkleCol.findOne({ hash });
		// 树存在
		if (hashTreeCol) {
			const hashTree = hashTreeCol.hashTree;
			hashTreeCol = new MerkleTree({ leafcount: parseInt(depth), hashTree })
			return hashTreeCol.getTreeNode(parseInt(depth), parseInt(index)).hash;
		}
		// 树不存在
		return '';
	}

	/**
	 * 获取文件元数据
	 * @param hash 文件HASH，采用sha256算法
	 */
	async getMetadata(hash: string): Promise<MetadataOptions> {
		await this.connect();
		return await this.filesCol.findOne({ hash });
	}

	/*
     * 获取已经上传的文件的片数
     */
	async getUploadedCount(hash: string): Promise<number> {
		await this.connect();
		return await this.col.count({ hash });
	}

	/**
	 * 获取不完整的资源文件
	 * Hash不匹配且上传日期为一周之前则认为是垃圾资源
	 * @param pagenumber 页码
	 * @param pagecount 页数
	 */
	async getUnIntegralResourceFiles(pagenumber: number = 0, pagecount: number = 20): Promise<MetadataOptions[]> {
		await this.connect();
		let tnow = Date.now();
		let tspan = this.unIntergalTimeSpan;
		let files = await this.filesCol
			.find({
				$where: function () {
					return this.hash !== this.root && this.ctime + tspan < tnow;
				}
			})
			.skip(pagenumber)
			.limit(pagecount)
			.toArray();
		return files;
	}
	/**
	 * 删除用户上传的资源文件
	 * @param hash HASH
	 */
	async removeResourceFile(hash: string) {
		await this.connect();
		await this.filesCol.deleteOne({ hash });
		await this.col.deleteMany({ hash });
		await this.merkleCol.deleteOne({ hash })
	}
	/**
	 * 删除全部不完整的资源文件
	 */
	async removeUnIntegralResourceFiles() {
		await this.connect();
		let files = await this.getUnIntegralResourceFiles();
		if (files && files.length > 0) {
			for (let i = 0; i < files.length; i++) {
				const ele = files[i];
				await this.removeResourceFile(ele.hash);
			}
			// 每次默认删除20个，递归进行
			await this.removeUnIntegralResourceFiles();
		}
	}
}
